package com.szm.sentinelx.slots.defaults.authority;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.szm.sentinelx.slots.config.SentinelXConfig;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;


/**
 * @author: shaozeming
 * @date: 2022/8/12 23:04
 * @description:
 **/
public class DefaultAuthorityRuleManager {


    private static Set<AuthorityRule> authorityRules = new HashSet<>();

    private static Map<String, Set<AuthorityRule>> authorityRuleBack = new ConcurrentHashMap<>();

    private static final RulePropertyListener LISTENER = new RulePropertyListener();
    private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();

    static final StampedLock lock = new StampedLock();

    static {
        currentProperty.addListener(LISTENER);
    }

    public static void register2Property(SentinelProperty<List<AuthorityRule>> property) {
        AssertUtil.notNull(property, "property cannot be null");
        synchronized (LISTENER) {
            if (currentProperty != null) {
                currentProperty.removeListener(LISTENER);
            }
            property.addListener(LISTENER);
            currentProperty = property;
            RecordLog.info("[DefaultAuthorityRuleManager] Registering new property to authority rule manager");
        }
    }




    static  Set<AuthorityRule> getAuthorityRules(String name) {
        if(SentinelXConfig.isEnableCopy()){
            return getByCopy(name);
        }
        return authorityRules;
    }


    private static Set<AuthorityRule> getByCopy(String name){
        if(authorityRuleBack.get(name) !=null ){
            return authorityRuleBack.get(name);
        }

        Set<AuthorityRule> authorityRuleSet=new HashSet<>();
        long stamp = lock.tryReadLock();
        if(stamp<= 0){
          return authorityRuleSet;
        }
        try {
            for (AuthorityRule authorityRule: authorityRules){
                authorityRuleSet.add(AuthorityCopy.replace(name,authorityRule));
            }
            authorityRuleBack.put(name,authorityRuleSet);
        }finally {
            lock.unlockRead(stamp);
        }
        return authorityRuleSet;
    }

    /**
     * Load the authority rules to memory.
     *
     * @param rules list of authority rules
     */
    public static void loadRules(List<AuthorityRule> rules) {
        currentProperty.updateValue(rules);
    }

    public static  Map<String, Set<AuthorityRule>>  getBackRules() {
        return authorityRuleBack;
    }


    private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {

        @Override
        public void configUpdate(List<AuthorityRule> conf) {
            loadAuthorityConf(conf);
        }

        private void loadAuthorityConf(List<AuthorityRule> list) {

            long stamp = lock.writeLock();
             try {
                 Set<AuthorityRule> newRuleSet = new HashSet<>();

                 if (list == null || list.isEmpty()) {
                     authorityRules.clear();
                     authorityRuleBack.clear();
                     RecordLog.info("[DefaultAuthorityRuleManager] Load authority rules: " + authorityRules);
                     return;
                 }

                 for (AuthorityRule rule : list) {
                     if (!isValidRule(rule)) {
                         RecordLog.warn("[DefaultAuthorityRuleManager] Ignoring invalid authority rule when loading new rules: " + rule);
                         continue;
                     }

                     if (StringUtil.isBlank(rule.getLimitApp())) {
                         rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
                     }

                     newRuleSet.add(rule);
                 }

                 authorityRules.clear();
                 authorityRules.addAll(newRuleSet);

                 Map<String,Set<AuthorityRule>> defaultRuleMap=new ConcurrentHashMap<>();
                 for (String name: authorityRuleBack.keySet()){
                     Set<AuthorityRule> defaultRuleList=new HashSet<>();
                     for (AuthorityRule authorityRule: authorityRules){
                         defaultRuleList.add(AuthorityCopy.replace(name,authorityRule));
                     }
                     defaultRuleMap.put(name,defaultRuleList);
                 }
                 authorityRuleBack.clear();
                 authorityRuleBack.putAll( defaultRuleMap);
                 RecordLog.info("[DefaultAuthorityRuleManager] Load authority rules: " + authorityRules);
             }finally {
                 lock.unlockWrite(stamp);
             }
        }

        @Override
        public void configLoad(List<AuthorityRule> value) {
             loadAuthorityConf(value);


        }


    }

    public static boolean isValidRule(AuthorityRule rule) {
        return rule != null && !StringUtil.isBlank(rule.getResource())
                && rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp());
    }


    static boolean passCheck(AuthorityRule rule, Context context) {
        String requester = context.getOrigin();

        // Empty origin or empty limitApp will pass.
        if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
            return true;
        }

        // Do exact match with origin name.
        int pos = rule.getLimitApp().indexOf(requester);
        boolean contain = pos > -1;

        if (contain) {
            boolean exactlyMatch = false;
            String[] appArray = rule.getLimitApp().split(",");
            for (String app : appArray) {
                if (requester.equals(app)) {
                    exactlyMatch = true;
                    break;
                }
            }

            contain = exactlyMatch;
        }

        int strategy = rule.getStrategy();
        if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
            return false;
        }

        if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
            return false;
        }

        return true;
    }
}
